#! /usr/bin/perl -w

use strict;
use utf8;           # to support hanzi regex match
use Encode;
use MP3::Info;      # to get the length of mp3
use Audio::WMA;     # to get the length of wma
use Net::DBus qw(:typing); # to send/close notification
use File::Find;
use Getopt::Long;
use File::Basename;
use Cwd qw/:DEFAULT abs_path/;
use Term::ANSIColor qw(:constants);
use Algorithm::Numerical::Shuffle qw /shuffle/;
# use Smart::Comments '####';

no warnings 'File::Find';
$Term::ANSIColor::AUTORESET = 1;
#$| = 1;

$SIG{INT} = $SIG{TERM} = sub { exit 0 };
!system( qq{which mplayer >/dev/null} ) or die qq{Can not found mplayer};

my $bus; #= Net::DBus->session;
my $svc; #= $bus->get_service("org.freedesktop.Notifications");
my $obj; #= $svc->get_object("/org/freedesktop/Notifications");
my $notification_id;

my $profile = $ENV{HOME} . "/.myplayer.list";
my ( @found, @play, @repo );
my %opt;

GetOptions (
    'directory|d=s'    => \$opt{'dir'},
    'regexp|r=s'       => \$opt{'reg'},
    'tip|t'            => \$opt{'tip'},
    'sort|s'           => \$opt{'sor'},
    'verbose|v'        => \$opt{'ver'},
    'maxdep|m=i'       => \$opt{'max'},
    'noninteract|n'    => \$opt{'int'},
    'position|p=i'     => \$opt{'pos'},
    'loop|l'           => \$opt{'loop'},
    'help|h'           => sub { help() },
);

## supported media type in default
$opt{reg} ||= $ARGV[0] || qr{\.(mp3|wma|mpg|wav|ogg|rmvb|rm|avi|wmv)$}io;
$opt{max} ||= 65535;
$opt{pos} ||= 0;

$opt{reg} = Encode::decode_utf8( $opt{reg} );

## make sure running in X
$ENV{DISPLAY} or $opt{'tip'} = 0;

## seems that these codes can not work in a tty
if ($opt{'tip'}) {
    $bus = Net::DBus->session;
    $svc = $bus->get_service("org.freedesktop.Notifications");
    $obj = $svc->get_object("/org/freedesktop/Notifications");
}

### opt: %opt

if ( defined $opt{'dir'} ) {
    my @r = glob( $opt{'dir'} );
    push @repo, abs_path( shift @r ) while (@r);
    &find_music( @repo );
}
else {
    &read_repo( \@repo );

    if ( @repo ) {
        &find_music( @repo );
    }
    else {
        &prompt_no_repo;
        exit 127;
    }
}

die "Not found\n" unless scalar @found;

if ( $opt{sor} ) {
    @found = sort sort_by_name @found if $opt{sor};
}
else {
    my $rand_cnt = 3;
    @found = shuffle @found while ( $rand_cnt-- );
}

unless ( $opt{int} ) {
    my $screen_col = qx{ tput cols }; chomp $screen_col;
    print "MUSIC FOUND\n";
    print '-' x  ($screen_col / 2), "\n";
    for my $i (0..$#found) {
        my $item = $found[$i];
        ($item = basename( $item )) =~ s{\.[^.]+$}{}o unless $opt{ver};
        printf("%6u) %s\n", $i, $item );
    }
    print '-' x ($screen_col / 2), "\n";
    print "Select (supported: [1 2 3 | 1..3 ]) [Default:ALL]: ";
    my $choice = <STDIN>;
    $choice = <STDIN> while ( ! &is_range( $choice ) );
}

@play =  0 .. $#found unless scalar( @play );
### to play: @play
&play( @play );

# ---------------------
sub find_music(@) {
    find( \&wanted, @_ )
}

#
sub wanted {
    ## fullname: $File::Find::name
    ## basename: $_
    ##return if /^\.+/;  
    # set utf8 flag on the string
    $_ = Encode::decode_utf8($_);
    push @found, $File::Find::name if $_ =~ m{$opt{'reg'}}io;
}

sub read_repo {
    my $roa_repo = shift;

    unless ( open( FH, "<", $profile ) ) {
        print qq{Can not open "$profile" for reading: $!\n};
        &prompt_no_repo;
        exit 1;
    }

    READ: while (<FH>) {
        ## ignore white lines and comments
        next if /^\s*$/;
        next if /^\s*#/;
        
        ## strip leading and trailling spaces
        s/^\s//g; s/\s+$//g;

        my @dirs = glob( $_ );
        for( @dirs ) {
            s!^!$ENV{HOME}! unless m{^/};
            for my $line (@{$roa_repo}) {
                next READ if $_ eq $line;
            }
            push @{$roa_repo}, $_ if -d;
        }
    }
    ### repo: @{$roa_repo}
}

sub prompt_no_repo {
    ( my $comment = <<'EOF' ) =~ s/^\s+/# /mg;
    One directory per line
    White lines and lines begin with '#' willbe ignored
    Wildcards '*'and '?' are supported
    We can use '~' instead of $HOME too
EOF

    ( my $msg = <<"EOF" ) =~ s/^\s+//mg;
    I do not know which directory to search in
    Is this your first time to run this script?
    Use '--directory | -d' option to specify or write it in $profile
EOF

    open( FH, ">", $profile ) or die qq(Can not open "$profile" for writing);
    print FH $comment;
    close FH or die $!;

    print STDERR $msg;
}

sub is_range($) {
    my $str = shift; chomp $str;
    return 1 if $str eq '';

    foreach (split /\s+/, $str) {
        if (/^\d+$/) {
            push @play, $_ if ( $_ >=0 and $_ <= $#found );
        }
        elsif (/^\d+\.\.\d+$/) {
            my @tmp = eval $_;
            push @play, @tmp if ( $tmp[0] >=0 and $tmp[-1] <= $#found );
        }
    }

    unless (@play) {
        print "\nTry again: ";
        return 0;
    }
    1;
}
sub play(@) {
    my @music = @_;

    ### will play: @found[ @music ]
    LINE: for ( @music ) {
        next unless -e $found[ $_ ];
        print BOLD BLUE "\nNo.$_\t$found[ $_ ]\n";
    
        ( my $basename = basename($found[ $_ ]) ) =~ s{\.[^\.]+$}{};
        if ( $opt{'tip'} ) {
            my $timeout = &get_time( $found[ $_ ] );
            #### timeout: $timeout
            my $t = int( $timeout / 1000 );
            if ( $t > 60 ) {
                my $minutes = int( $t / 60 );
                my $seconds = $t % 60;

                $t = $minutes . "m " . $seconds . "s";
            }
            my $summary = 'Huanlf@gmail.com';
            (my $msg = <<"EOF") =~ s/^\s+//mg;
            $basename
            $t  @  $_/$#music
EOF
            # $obj->CloseNotification( $notification_id ) if $notification_id;
            $notification_id = $obj->Notify(
                 $0,                    # app name
                 $notification_id || 0, # replace_id
                 'sound',               # icon
                 $summary,              # summary
                 $msg,                  # body
                 [],                    # action
                 {},                    # hint
                 0 #$timeout            # timeout
             );
        }

        ## show music name in taskbar and title bar print "\033]0;". $basename . "\007";
        system (qq{mplayer -nojoystick -nolirc -nortc -ss $opt{pos} "$found[ $_ ]" 2>/dev/null});
        ## sleep a while to receive the potential INT/TERM signals
        select( undef, undef, undef, 0.5 );
    }
    goto LINE if $opt{loop};
}

## sort songs
sub sort_by_name() {
    $a = $1 if $a =~ /^([0-9a-zA-Z]+)[-\.]/;
    $b = $1 if $b =~ /^([0-9a-zA-Z]+)[-_\.]/;
    return $a <=> $b if ( $a =~ /^\d+$/ and $b =~ /^d+$/ );
    return $a cmp $b;
}

## try to get the how long the song will play
sub get_time($) {
    my $default_sec = 3 * 60 * 1000;
    my $file = shift;
    my $ext  = $1 if $file =~ m{(mp3|wma)$}io;
    #### ext: $ext
    ## neither mp3 nor wma
    return $default_sec unless $ext;
    return $default_sec unless `file $file` =~ /MPEG|MP3|ASF/oi;

    if ( $ext =~ /mp3/oi ) {
        my $info = get_mp3info($file);
        return int($info->{SECS}) * 1000 || $default_sec;
    }
    else {
        my $wma  = Audio::WMA->new($file);
        my $info = $wma->info();
        return int($info->{playtime_seconds}) * 1000 || $default_sec;
    }
}

sub help {
    my $msg = <<"EOF";
    Usage: $0 [ options ] [ regexp ]
    Options:
    --directory   | -d:    which directory to search in
    --regexp      | -r     the regexp to match a file name (not necessary)
    --tip         | -t     notify when songs begin playing
    --sort        | -s     sort play list (random by default)
    --verbose     | -v     verbose
    --maxdep      | -m     how further we will search in a directory (not working now)
    --noninteract | -n     play all the music after searching without asking
    --position    | -p     seek to given time position
    --loop        | -l     loops playback
    --help        | -h     show this help messages
EOF

    warn "$msg\n" and exit 0;
}

END {
    ## close notification if needed
    $obj->CloseNotification( $notification_id ) if $notification_id;
    print "\033]0;" . "fei zhu" . "\007"        if $ENV{DISPLAY};
}
